"""
二阶导数处理器

计算光谱数据的二阶导数，用于进一步突出光谱的峰形特征。
二阶导数能够更好地分离重叠峰，提高光谱分辨率。
"""

import pandas as pd
import numpy as np
from scipy.signal import savgol_filter
from ..base import PreprocessorInterface
from ..validation import validate_input_data, safe_copy_dataframe


class SecondDerivative(PreprocessorInterface):
    """
    二阶导数处理器
    
    计算光谱数据的二阶导数，用于：
    1. 突出峰形特征
    2. 分离重叠峰
    3. 提高光谱分辨率
    4. 消除线性和常数基线
    """
    
    def __init__(self, method='savgol', window_length=11, polyorder=3):
        """
        初始化二阶导数参数
        
        Args:
            method (str): 计算方法，'savgol'或'diff'
            window_length (int): SG滤波窗口长度（仅用于savgol方法）
            polyorder (int): 多项式阶数（仅用于savgol方法，建议>=3）
        """
        self.method = method
        
        # 确保窗口长度为奇数
        if window_length % 2 == 0:
            window_length += 1
        
        self.window_length = window_length
        # 二阶导数建议使用至少3阶多项式
        self.polyorder = max(polyorder, 3)
    
    def process(self, data: pd.DataFrame) -> pd.DataFrame:
        """
        计算光谱数据的二阶导数
        
        Args:
            data (pd.DataFrame): 输入光谱数据
            
        Returns:
            pd.DataFrame: 二阶导数后的数据
        """
        # 验证输入数据
        validate_input_data(data)
        
        # 创建数据副本
        result = safe_copy_dataframe(data)
        
        # 获取光谱数据列（跳过第一列ID列）
        spectral_columns = result.columns[1:]
        spectral_data = result[spectral_columns].values
        
        # 计算二阶导数
        if self.method == 'savgol':
            derivative_spectra = self._savgol_derivative(spectral_data)
        else:  # 使用简单差分方法
            derivative_spectra = self._diff_derivative(spectral_data)
        
        # 将导数数据放回DataFrame
        result[spectral_columns] = derivative_spectra
        
        return result
    
    def _savgol_derivative(self, spectral_data):
        """使用Savitzky-Golay方法计算二阶导数"""
        n_samples, n_points = spectral_data.shape
        
        # 检查窗口长度
        window_length = min(self.window_length, n_points)
        if window_length % 2 == 0:
            window_length -= 1
        window_length = max(window_length, self.polyorder + 1)
        
        derivative_spectra = np.zeros_like(spectral_data)
        
        for i in range(n_samples):
            spectrum = spectral_data[i, :]
            
            try:
                # 使用SG滤波器计算二阶导数
                derivative = savgol_filter(
                    spectrum,
                    window_length=window_length,
                    polyorder=self.polyorder,
                    deriv=2,  # 二阶导数
                    mode='nearest'
                )
                derivative_spectra[i, :] = derivative
            except Exception as e:
                # 如果SG方法失败，使用差分方法
                print(f"SG二阶导数失败，样本 {i}，使用差分方法: {e}")
                derivative_spectra[i, :] = self._diff_single_spectrum(spectrum)
        
        return derivative_spectra
    
    def _diff_derivative(self, spectral_data):
        """使用差分方法计算二阶导数"""
        n_samples, n_points = spectral_data.shape
        derivative_spectra = np.zeros_like(spectral_data)
        
        for i in range(n_samples):
            spectrum = spectral_data[i, :]
            derivative_spectra[i, :] = self._diff_single_spectrum(spectrum)
        
        return derivative_spectra
    
    def _diff_single_spectrum(self, spectrum):
        """对单个光谱计算差分二阶导数"""
        # 二阶导数 = 一阶导数的一阶导数
        # 或者直接计算：d2[i] = x[i-1] - 2*x[i] + x[i+1]
        
        derivative = np.zeros_like(spectrum)
        n_points = len(spectrum)
        
        if n_points < 3:
            return derivative  # 数据点太少，无法计算二阶导数
        
        # 中间点使用中心差分：d2[i] = x[i-1] - 2*x[i] + x[i+1]
        for i in range(1, n_points - 1):
            derivative[i] = spectrum[i-1] - 2*spectrum[i] + spectrum[i+1]
        
        # 边界点处理
        # 第一个点
        derivative[0] = spectrum[0] - 2*spectrum[1] + spectrum[2]
        
        # 最后一个点
        derivative[-1] = spectrum[-3] - 2*spectrum[-2] + spectrum[-1]
        
        return derivative
    
    def get_name(self) -> str:
        """返回处理器名称"""
        if self.method == 'savgol':
            return f"二阶导数(SG窗口{self.window_length})"
        else:
            return "二阶导数(差分)"
    
    def get_id(self) -> int:
        """返回唯一ID"""
        return 8